﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;
using System.Diagnostics;
using APIKeysLib;
using Newtonsoft.Json;

namespace NetLib
{
    public static class FitbitFetcher
    {
        #region Data and Properties
        public const string RequestTokenURL = "http://api.fitbit.com/oauth/request_token";
        public const string AccessTokenURL = "http://api.fitbit.com/oauth/access_token";
        public const string AuthorizeTokenURL = "http://api.fitbit.com/oauth/authorize";
        private static OAuthBase Auth = new OAuthBase();

        private const string WEIGHT = "body/weight";
        private const string FOOD = "foods/log/caloriesIn";
        private const string EXERCISE = "activities/log/calories";


        #endregion //Data and Properties

        #region Constructors

        static FitbitFetcher()
        {

        }

        #endregion //Constructors

        #region Methods


        [Pure]
        private static string SignedRequestPOST(string parameters, string URL, string token = "", string tokenSecret = "")
        {
            string normalizedURL;
            string normalizedRequestParameters;

            string signature = Auth.GenerateSignature(new Uri(URL + parameters), FitbitAPIKeys.ConsumerKey,
                FitbitAPIKeys.ConsumerSecret, token, tokenSecret, "POST", Auth.GenerateTimeStamp(),
                Auth.GenerateNonce(), out normalizedURL, out normalizedRequestParameters);

            Debug.WriteLine(signature);
            Debug.WriteLine(normalizedURL);
            Debug.WriteLine(normalizedRequestParameters);

            //string results = RESTProcessor.HttpPost(normalizedURL, normalizedRequestParameters + "&oauth_signature=" + signature);
            string results = RESTProcessor.HttpPost(normalizedURL, normalizedRequestParameters
                + "&oauth_signature=" + signature);

            return results;

        }

        [Pure, Obsolete("Not Actually obsolete, but this should not yet be used")]
        private static string SignedRequestPOSTWithHeader(string parameters, string URL, string token = "", string tokenSecret = "")
        {
            string normalizedURL;
            string normalizedRequestParameters;

            string signature = Auth.GenerateSignature(new Uri(URL + parameters), FitbitAPIKeys.ConsumerKey,
                FitbitAPIKeys.ConsumerSecret, token, tokenSecret, "POST", Auth.GenerateTimeStamp(),
                Auth.GenerateNonce(), out normalizedURL, out normalizedRequestParameters);

            List<string> headerParams = normalizedRequestParameters.Split('&').ToList();
            headerParams.Add(string.Format("oauth_signature={0}", signature));

            string results = RESTProcessor.HttpPost(normalizedURL, headerParams, true);

            return results;


        }




        [Pure]
        private static string SignedRequestGET(string parameters, string URL, string token = "", string tokenSecret = "")
        {
            string normalizedURL;
            string normalizedRequestParameters;

            string signature = Auth.GenerateSignature(new Uri(URL + "?" + parameters), FitbitAPIKeys.ConsumerKey,
                FitbitAPIKeys.ConsumerSecret, token, tokenSecret, "GET", Auth.GenerateTimeStamp(),
                Auth.GenerateNonce(), out normalizedURL, out normalizedRequestParameters);

            Debug.WriteLine(signature);
            Debug.WriteLine(normalizedURL);
            Debug.WriteLine(normalizedRequestParameters);

            //string results = RESTProcessor.HttpPost(normalizedURL, normalizedRequestParameters + "&oauth_signature=" + signature);
            string results = RESTProcessor.HttpGet(normalizedURL + "?" + normalizedRequestParameters
                + "&oauth_signature=" + signature);

            return results;

        }
        [Pure]
        private static string SignedRequestGETWithHeader(string parameters, string URL, string token = "", string tokenSecret = "")
        {
            string normalizedURL;
            string normalizedRequestParameters;

            string signature = Auth.GenerateSignature(new Uri(URL + "?" + parameters), FitbitAPIKeys.ConsumerKey,
                FitbitAPIKeys.ConsumerSecret, token, tokenSecret, "GET", Auth.GenerateTimeStamp(),
                Auth.GenerateNonce(), out normalizedURL, out normalizedRequestParameters);

            Debug.WriteLine("####");
            Debug.WriteLine(signature);
            Debug.WriteLine("####");
            Debug.WriteLine(normalizedURL);
            Debug.WriteLine(normalizedRequestParameters);

            List<string> headerParams = normalizedRequestParameters.Split('&').ToList();
            headerParams.Add(string.Format("oauth_signature={0}", signature));

            //string results = RESTProcessor.HttpPost(normalizedURL, normalizedRequestParameters + "&oauth_signature=" + signature);
            string results = RESTProcessor.HttpGet(normalizedURL, headerParams);

            return results;

        }

        #region User Authentication
        [Pure]
        private static string GetRequestToken()
        {
            return SignedRequestPOST("", RequestTokenURL, "", "");
        }

        //[Pure]
        //private static string GetRequestURL()
        //{

        //}

        [Pure]
        public static Dictionary<string, string> GetRequestCredentials()
        {
            Dictionary<string, string> results = new Dictionary<string, string>();

            var tokens = GetRequestToken()
                .Split('&')
                .Select(x => x.Split('=')).ToArray();

            results.Add(tokens[0][0], tokens[0][1]);
            results.Add(tokens[1][0], tokens[1][1]);
            results.Add(tokens[2][0], tokens[2][1]);
            results.Add("OAuthRequestURL", string.Format("{0}?oauth_token={1}", AuthorizeTokenURL, tokens[0][1]));

            return results;
        }


        [Pure]
        public static string[] GetAccessTokens(string request_token, string request_token_secret,
            string oauth_verifier)
        {

            string results = SignedRequestPOST(string.Format("?oauth_verifier={0}", oauth_verifier), AccessTokenURL,
                request_token, request_token_secret);





            return results.Split('&');


            //return results;
        }

        #endregion //User Authentication

        #region Data Fetching
        [Pure]
        public static string GetBodyMeasurements(string accessToken, string accessTokenSecret, string date, bool useJSON = true)
        {
            string apiVersion = "1";
            string userID = "-";// "227TVC";// "-"; //indicates that userID is to be inferred from OAuth
            string AcceptLanguage = "US";



            string URL = string.Format("http://api.fitbit.com/{0}/user/{1}/body/date/{2}.{3}", apiVersion, userID, date,
                useJSON ? "json" : "xml");
            string parameters = "";// string.Format("Accept-Language={0}", AcceptLanguage);

            string results = SignedRequestGETWithHeader(parameters, URL, accessToken, accessTokenSecret);

            return results;

        }


        [Pure]
        public static string GetTimeSeries(string accessToken, string accessTokenSecret,
            string resourcePath, bool useJSON = true)
        {
            string apiVersion = "1";
            string userID = "-";
            string AcceptLanguage = "US";
            int retries = 5;
            string results;

            do
            {
                string URL = string.Format("http://api.fitbit.com/{0}/user/{1}/{2}/date/{3}/1y.{4}",
                    apiVersion, userID, resourcePath, "today",
                    useJSON ? "json" : "xml");


                results = SignedRequestGETWithHeader("", URL, accessToken, accessTokenSecret);
                retries--;
            } while (string.IsNullOrEmpty(results) && retries > 0);

            return results;
        }


        public static IEnumerable<FitbitWeightResults> FetchWeight(string accessToken, string accessTokenSecret,
            bool useJSON = true)
        {
            string response;
            int retries = 5;

            //do
            //{
                response = GetTimeSeries(accessToken, accessTokenSecret, WEIGHT);
            //    retries--;
            //} while (string.IsNullOrEmpty(response) && retries > 0);

            FitbitWeightTimeSeriesResults weight =
                JsonConvert.DeserializeObject<FitbitWeightTimeSeriesResults>(response);
            //JsonConvert.DeserializeAnonymousType(response);

            return weight.BodyWeight.Select(x => new FitbitWeightResults(x.dateTime, x.value));



        }

        public static IEnumerable<FitbitFoodIntakeResults> FetchFoodIntake(string accessToken, string accessTokenSecret, bool useJSON = true)
        {
            string response = GetTimeSeries(accessToken, accessTokenSecret, FOOD);

            FitbitFoodIntakeTimeSeriesResults food =
                JsonConvert.DeserializeObject<FitbitFoodIntakeTimeSeriesResults>(response);
            var temp =
                food.FoodIntake.Where(y => y.value != 0.0).Select(x => new FitbitFoodIntakeResults(x.dateTime, x.value));
            return temp;

        }


        public static IEnumerable<FitbitExerciseResults> FetchExercise(string accessToken, string accessTokenSecret, bool useJSON = true)
        {
            string response = GetTimeSeries(accessToken, accessTokenSecret, EXERCISE);

            FitbitExerciseTimeSeriesResults exercise =
                JsonConvert.DeserializeObject<FitbitExerciseTimeSeriesResults>(response);
            var temp =
                exercise.Exercise.Where(y => y.value != 0.0).Select(x => new FitbitExerciseResults(x.dateTime, x.value));
            return temp;


        }

        #endregion //Data Fetching





        #endregion //Methods




    }
    #region Data holder classes

    #region      Weight Classes

    internal class FitbitWeightTimeSeriesResults
    {
        private FitbitWeightBodyWeight[] _BodyWeight;

        [JsonProperty("body-weight")]
        public FitbitWeightBodyWeight[] BodyWeight
        {
            get
            {
                return _BodyWeight;
            }
            set
            {
                _BodyWeight = value;
            }
        }


    }

    internal class FitbitWeightBodyWeight
    {
        public DateTime dateTime;
        public double value;

    }

    public class FitbitWeightResults : IWeightResults
    {
        #region Data and Properties
        private double _Weight;
        public double Weight
        {
            get { return _Weight; }
            private set { _Weight = value; }
        }

        private DateTime _Date;
        public DateTime Date
        {
            get
            {
                return _Date;
            }
            private set
            {
                _Date = value;
            }
        }
        #endregion //Data and Properties

        #region Constructors

        public FitbitWeightResults(DateTime date, double weightInKG)
        {
            Date = date;

            //convert to pounds
            Weight = weightInKG * 2.20462262;
        }

        #endregion //Constructors

        #region Methods

        public override string ToString()
        {
            return string.Format("{0}: {1:0.0} lbs", Date.ToShortDateString(), Weight);
        }

        #endregion //Methods

    }


    #endregion //Weight Classes

    #region      FoodIntake Classes

    internal class FitbitFoodIntakeTimeSeriesResults
    {
        private FitbitFoodIntakeBody[] _FoodIntake;

        [JsonProperty("foods-log-caloriesIn")]
        public FitbitFoodIntakeBody[] FoodIntake
        {
            get
            {
                return _FoodIntake;
            }
            set
            {
                _FoodIntake = value;
            }
        }


    }

    internal class FitbitFoodIntakeBody
    {
        public DateTime dateTime;
        public double value;

    }

    public class FitbitFoodIntakeResults : IFoodIntakeResults
    {
        #region Data and Properties
        private double _Calories;
        public double CaloriesEaten
        {
            get { return _Calories; }
            private set { _Calories = value; }
        }

        private DateTime _Date;
        public DateTime Date
        {
            get { return _Date; }
            private set { _Date = value; }
        }
        #endregion //Data and Properties

        #region Constructors

        public FitbitFoodIntakeResults(DateTime date, double calories)
        {
            Date = date;

            //convert to pounds
            CaloriesEaten = calories;
        }

        #endregion //Constructors

        #region Methods

        public override string ToString()
        {
            return string.Format("{0}: {1:0.0} Calories", Date.ToShortDateString(), CaloriesEaten);
        }

        #endregion //Methods

    }

    #endregion //FoodIntake Classes

    #region      Exercise Classes
    internal class FitbitExerciseTimeSeriesResults
    {
        private FitbitExerciseBody[] _Exercise;

        [JsonProperty("activities-log-calories")]
        public FitbitExerciseBody[] Exercise
        {
            get
            {
                return _Exercise;
            }
            set
            {
                _Exercise = value;
            }
        }


    }

    internal class FitbitExerciseBody
    {
        public DateTime dateTime;
        public double value;

    }

    public class FitbitExerciseResults : IExerciseResults
    {
        #region Data and Properties
        private double _Calories;
        public double CaloriesBurned
        {
            get { return _Calories; }
            private set { _Calories = value; }
        }

        private DateTime _Date;
        public DateTime Date
        {
            get { return _Date; }
            private set { _Date = value; }
        }
        #endregion //Data and Properties

        #region Constructors

        public FitbitExerciseResults(DateTime date, double calories)
        {
            Date = date;

            CaloriesBurned = calories;
        }

        #endregion //Constructors

        #region Methods

        public override string ToString()
        {
            return string.Format("{0}: {1:0.0} Calories", Date.ToShortDateString(), CaloriesBurned);
        }

        #endregion //Methods

    }
    #endregion //Exercise Classes





    #endregion //Data holder classes
}
